1장. 결합과 시스템 설계
출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 장연호 옮김, 제이펍 2026) | 공식: https://www.jpub.kr/ | 원서: Manning
"결합(coupling)"은 흔히 나쁜 설계의 동의어처럼 쓰인다. 하지만 결합이 0인 시스템은 아예 작동하지 않는다. 이 장은 결합이 무엇이고, 어떻게 시스템을 묶어주는 "접착제" 역할을 하는지, 두려워하기보다 설계 도구로 다루는 관점을 잡아준다.
학습 목표
이 장을 끝내면 다음을 할 수 있다.
- 결합(coupling)을 어원·정의 차원에서 설명한다.
- 결합의 두 동인(공유 수명주기·공유 지식)을 구분하고 각각이 변경 전파에 어떻게 작용하는지 분석한다.
- 같은 기능을 공유 지식 양이 다른 세 가지 인터페이스로 재설계한다(MySQLRepository → IRepository(SQL) → IRepository(Save/Query)).
- 시스템의 세 구성요소(구성요소·상호작용·목적)와 결합의 관계를 설명한다.
- 코드베이스에서 "필수 결합"과 "우연 결합"의 차이를 판단한다.
전체 흐름도
[ 결합이란? — 라틴어 copulare = "함께 + 고정" = 연결 ]
│
▼
[ 결합의 규모 = 얼마나 강하게 함께 변하는가 ]
│
├─ 공유 수명주기 (같이 빌드·테스트·배포되는가)
└─ 공유 지식 (서로에 대해 얼마나 아는가)
│
▼
[ 지식 흐름 ]
상위(upstream) ── 기능 제공
▲
│ 하위가 상위의 인터페이스를 "안다"
│
하위(downstream) ── 기능 소비
▼
[ 시스템 = 구성요소 + 상호작용 + 목적 ]
결합 = 구성요소를 묶어 목적을 달성하게 하는 "접착제"
▼
[ 핵심 결론 ]
결합 0 = 불가능 + 무용. 다만 결합도 *필수*인지 *우연*인지 구분해야 한다.
"필수는 신중히, 우연은 제거" → 이후 장의 모든 설계 원리의 토대.
0. 사전 필수 용어
- 결합 (Coupling) — 두 구성요소가 연결되어 있어 서로 영향을 주는 관계. "결합됨" = "연결됨"으로 바꿔 읽어도 무방.
- 공유 수명주기 (Shared Lifecycle) — 두 구성요소가 함께 빌드·테스트·배포되는 정도. 한 모놀리스 안에 있으면 강함, 별 서비스로 떨어지면 약함.
- 공유 지식 (Shared Knowledge) — 한쪽이 다른 쪽의 내부에 대해 알아야 하는 양. 인터페이스 서명·기능 요구·구현 세부·환경 가정 등 다양한 형태.
- 상위 (Upstream) / 하위 (Downstream) — 기능을 제공하는 쪽이 상위, 소비하는 쪽이 하위. 종속성 화살표는 하위→상위, 지식 흐름은 그 반대(상위→하위).
- 시스템 (System) — 메도즈의 정의: "무언가를 달성하는 방식으로 구성된 상호 연결된 요소들의 집합." 세 요소: 구성요소·상호작용·목적.
- 필수 결합 vs 우연 결합 — 시스템의 목적상 반드시 필요한 결합과, 설계 부주의로 우연히 생긴 결합. 후자가 제거 대상.
- 허용 오차 (Tolerance) — 기계공학 비유. 결합부의 허용 가능한 변화 한계. 너무 빡빡하면 깨지고, 너무 헐거우면 흔들린다.
- 캡슐화 경계 (Encapsulation Boundary) — 구성요소가 외부에 드러내는 면과 숨기는 면의 구분선. 경계 바깥으로 새어나온 지식이 곧 우연 결합의 씨앗이 된다.
1. 결합이란? — 어원에서 본질로
결합은 영어 "couple"의 라틴어 어원 copulare에서 왔다. co(함께) + apere(고정) — "함께 고정한다"는 뜻이다. 즉 두 개체가 어떤 식으로든 연결되어 서로에게 영향을 줄 수 있는 모든 관계가 결합이다.
라틴어: co(함께) + apere(고정)
└──▶ copulare / copula
└──▶ 프랑스 고어: cople / copler
└──▶ 영어: couple → coupling
이 정의는 의외로 광범위하다.
- 시계 — 기어·스프링이 결합돼 시간을 측정한다.
- 자동차 — 엔진·차축·바퀴·브레이크가 결합돼 굴러간다.
- 인체 — 장기들이 결합돼 살아 있는 유기체가 된다.
- 우주 — 천체들은 중력으로 결합돼 있다(거리가 아무리 멀어도).
소프트웨어도 다르지 않다. 데이터베이스에 "강하게 결합된" 객체는 데이터베이스에 "강하게 연결된" 객체다. 두 마이크로서비스가 "느슨하게 결합됐다"는 말은 둘이 연결은 돼 있지만 의존이 얇다는 뜻일 뿐, 완전 독립이라는 뜻이 아니다.
핵심 통찰 — "결합 = 나쁘다"가 아니다. 결합 = 연결이고, 연결이 없으면 시스템은 존재할 수 없다. 진짜 질문은 "결합이 있냐 없냐"가 아니라 "어떤 종류의 결합이 얼마나 있냐"다.
비유로 쉽게 — 레고와 종이접기
레고 블록은 돌기(스터드)와 구멍(튜브)으로 결합된다. 결합 자체는 변하지 않지만, 어떻게 연결되는지(겹쳐 쌓는지·옆으로 붙이는지·플레이트로 잇는지)에 따라 만들 수 있는 모양이 달라진다.
반면 종이접기는 접힌 선으로 부분들이 결합된다. 한 접힌 선을 펴면 옆 선도 펴진다 — 강한 결합. 분리 가능한 레고와 달리 종이접기는 분리 자체가 형태 파괴다.
소프트웨어도 같은 기능을 레고처럼 만들 수도 있고 종이접기처럼 만들 수도 있다. 어떤 결합 방식을 고르느냐가 이 책의 주제다.
2. 결합의 규모 — 무엇이 결합을 강하게/약하게 만드는가
결합의 규모는 "두 구성요소가 얼마나 자주 함께 변경되어야 하느냐"로 측정된다. 두 가지 동인이 있다.
2.1 공유 수명주기 — 같이 빌드·테스트·배포되는가
같은 모놀리스 안의 모듈들은 수명주기가 같다. 한 곳을 고치면 전체를 다시 빌드·테스트·배포해야 한다. 별 서비스로 떼어 놓으면 각자 독립적으로 배포할 수 있다.
[ 강한 수명주기 결합 — 모놀리스 ]
┌─────────────────────────────────────────┐
│ 하나의 배포 단위 │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Payments │ │ Authorization│ │
│ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────┘
같이 빌드·테스트·배포된다.
[ 약한 수명주기 결합 — 서비스 분리 ]
┌──────────────┐ ┌──────────────────────┐
│ Billing 서비스│ │ Identity & Access 서비스│
│ (Payments) │ │ (Authorization) │
└──────────────┘ └──────────────────────┘
각자 독립 배포.
일상 비유 — 결혼한 부부의 일정과, 일주일에 한 번 만나는 친구 관계. 부부는 일정 변경 하나에도 양쪽이 다 영향받는다(강한 수명주기 결합). 친구는 약속 잡을 때만 동기화한다(약한 결합).
주의 — 캡슐화 경계 외에도 구조적·조직적 요소(팀 구조, 배포 파이프라인, 레포지토리 분리 여부 등)가 수명주기 결합에 영향을 준다. 8장 '거리'에서 더 자세히 다룬다.
2.2 공유 지식 — 서로에 대해 얼마나 알아야 하는가
같은 기능(고객 저장)을 세 가지 방식으로 설계해 보자. 결합의 형태가 어떻게 달라지는지 본다.
잘못된 예 / 올바른 예 — 저장소 인터페이스 3단계
A. 가장 많은 지식 공유 — 데이터베이스 종류까지 노출
# 잘못된 예: CustomersService 가 "MySQL"이라는 구체 제품을 안다
class CustomersService:
def __init__(self, repo: MySQLRepository): # ← 구체 클래스에 의존
self.repo = repo
def register(self, customer):
self.repo.execute_sql(
"INSERT INTO customers (name, email) VALUES (%s, %s)",
(customer.name, customer.email)
)
문제: MySQL을 PostgreSQL이나 DynamoDB로 바꾸려면 CustomersService 코드까지 손대야 한다. 데이터베이스 선택이라는 지식이 캡슐화되지 않고 밖으로 새어 나와 있다.
B. 중간 단계 — 추상 인터페이스, 하지만 여전히 "SQL"을 안다
class IRepository: # 추상 인터페이스
def begin_transaction(self): ...
def execute_sql(self, query, params): ... # ← SQL 가정이 남음
def commit(self): ...
def rollback(self): ...
class CustomersService:
def __init__(self, repo: IRepository): # 인터페이스에 의존
self.repo = repo
def register(self, customer):
self.repo.execute_sql(
"INSERT INTO customers (name, email) VALUES (%s, %s)",
(customer.name, customer.email)
)
진전: 구체 데이터베이스 제품은 모르지만, 여전히 "관계형 DB 집단"을 가정한다. MongoDB·Redis 같은 키-값/문서 저장소로 바꾸면 깨진다.
C. 가장 적은 지식 공유 — 의도만 노출
class IRepository:
def save(self, entity): ... # 무엇으로 저장하는지 모름
def query(self, criteria): ... # 어떻게 검색하는지 모름
class CustomersService:
def __init__(self, repo: IRepository):
self.repo = repo
def register(self, customer):
self.repo.save(customer) # SQL 도, 명령 형태도 안다는 가정 없음
def find_active(self):
return self.repo.query({"status": "active"})
결과: 저장소가 MySQL이든 MongoDB든 메모리든 CustomersService 입장에선 동일. 공유되는 지식은 "고객 저장이 필요하다"는 의도뿐이다.
참고 — A→B→C로 갈수록 공유 지식이 줄어 변경 전파 빈도도 준다. 다만 무조건 C가 좋은 건 아니다. 너무 추상화하면 정작 필요한 정보까지 가려진다(예: 트랜잭션 경계를 통제해야 하는 금융 시스템). 적절한 수준을 고르는 게 설계다.
2.3 암묵적 지식 — 더 위험한 형태
명시적으로 공유 안 했는데도 암묵적으로 가정하는 지식이 있다.
- "운영체제는 Linux일 거야" — Windows 배포에서 폭발
- "이 API는 항상 5초 안에 응답해" — 네트워크 지연에서 폭발
- "이 큐는 절대 비어 있지 않아" — 새벽 4시 트래픽에서 폭발
실무 체크 포인트 — 코드 리뷰에서 "이건 항상 X일 거야"가 들리면, 그 가정이 인터페이스에 명시돼 있는지 확인하라. 명시 안 됐다면 그게 암묵적 결합이다.
3. 지식의 흐름 — 상위와 하위
결합된 두 구성요소 사이에는 지식 흐름의 방향이 있다.
지식 흐름 ← ← ← ← ← ← ← ←
┌──────────────────┐ ┌──────────┐
│ Distribution │ ──▶│ CRM │
│ (하위/downstream)│ │(상위/upstream)│
└──────────────────┘ └──────────┘
종속성 화살표 → → → → → → → → →
- 상위 (Upstream) — 기능을 제공. CRM은 고객 데이터를 제공한다.
- 하위 (Downstream) — 기능을 소비. Distribution은 CRM을 호출해 고객 정보를 가져온다.
종속성과 지식은 방향이 반대다. 하위가 상위의 인터페이스를 알고 있어야 호출할 수 있기 때문이다.
비유 — 식당과 손님
식당(상위)은 메뉴라는 인터페이스를 제공한다. 손님(하위)이 식당의 주방 내부 구조를 알 필요는 없지만 메뉴는 알아야 주문할 수 있다. 메뉴가 바뀌면 손님은 그걸 알게 되어야 한다(지식 흐름: 식당 → 손님). 손님이 바뀌어도 식당은 모른다(역방향 흐름 없음).
소프트웨어에서 상위 인터페이스 변경이 위험한 이유가 여기다. 하위는 자기도 모르는 사이 깨질 수 있다.
4. 시스템이란 무엇인가
도넬라 메도즈의 정의: "무언가를 달성하는 방식으로 구성된 상호 연결된 요소들의 집합."
세 구성요소가 서로 의존한다.
목적
↗ ↘
필요로 허용
↗ ↘
구성요소 ─── 상호작용
- 구성요소 — 부분(서비스·모듈·클래스·메서드·문장).
- 상호작용 — 부분들이 어떻게 협력하는가(=결합).
- 목적 — 왜 협력하는가(비즈니스 가치).
세 중 하나를 바꾸면 나머지가 영향받는다. 기능 추가(목적 변경) → 새 모듈 필요(구성요소 변경) → 모듈 간 호출 변경(상호작용 변경).
루스 맬런(Ruth Malan)의 통찰 — "시스템 설계는 본질적으로 경계(내부에 있는 것, 외부에 있는 것, 걸쳐 있는 것, 그 사이를 이동하는 것)와 상충 관계에 관한 것이다." 상자(구성요소)만큼이나 상자 사이의 선(상호작용=결합)을 정교하게 설계해야 한다.
시스템의 프랙털 본질
소프트웨어 시스템은 시스템의 시스템이다.
[ 회사 전체 ]
└─ [ Processor 서비스 ] ← 자체로도 시스템
└─ [ DataAccess 클래스 ] ← 자체로도 시스템
└─ [ NextRequest() 메서드 ] ← 자체로도 시스템
└─ [ if/return 문장들 ]
같은 결합 원리가 모든 층위에 적용된다. 메서드 안의 if 분기끼리도 결합돼 있고, 마이크로서비스 사이의 메시지 큐도 결합돼 있다. 이 프랙털 관점은 12장에서 본격적으로 다룬다.
5. 결합은 도구다 — 필수 vs 우연
이 장의 최종 메시지를 다시 정리하면:
| 구분 | 의미 | 처방 |
|---|---|---|
| 필수 결합 | 시스템 목적상 반드시 필요한 연결 (예: 주문 → 결제) | 신중하게 관리 |
| 우연 결합 | 설계 부주의·기술 선택 미스로 우연히 생긴 연결 (예: 화면 컴포넌트가 DB 스키마를 직접 안다) | 제거 |
참고 — 흔한 오해: "모든 의존성을 0으로 줄이자." 불가능하고 무용하다. 협력 없는 부품은 시스템이 아니라 부품 더미다. 목표는 결합 제거가 아니라 결합 질의 제어다.
실무 예제 — 우연 결합을 제거하는 한 줄 리팩토링
# 잘못된 예: HTTP 핸들러가 DB 스키마를 직접 의존
@app.post("/orders")
def create_order(request):
db.execute(
"INSERT INTO orders (user_id, items_json) VALUES (?, ?)",
(request.user_id, json.dumps(request.items))
)
return {"status": "ok"}
문제: 컬럼명을 items_json → items로만 바꿔도 핸들러가 깨진다. DB 스키마라는 지식이 핸들러 안으로 새 나왔다.
# 올바른 예: 도메인 레이어가 스키마를 캡슐화
@app.post("/orders")
def create_order(request):
order = Order.create(user_id=request.user_id, items=request.items)
order_repo.save(order)
return {"status": "ok"}
# order_repo 안에서만 컬럼명을 안다
class OrderRepo:
def save(self, order: Order):
db.execute(
"INSERT INTO orders (user_id, items_json) VALUES (?, ?)",
(order.user_id, order.items_serialized())
)
핸들러는 "주문을 저장한다"만 알고, 컬럼명은 OrderRepo만 안다. 스키마 변경이 더 이상 핸들러를 깨뜨리지 않는다.
6. (선택) 기계공학에서 배우는 비유 — 허용 오차
기계공학에서 두 부품을 결합할 때 절단면을 완벽하게 같게 만들기는 비싸고 종종 불가능하다. 그래서 의도적으로 허용 오차 (Tolerance)를 둔다.
- 허용 오차가 너무 작으면 — 작은 결함에도 결합이 안 됨(빡빡함).
- 허용 오차가 너무 크면 — 헐거워서 흔들림(느슨함).
소프트웨어 인터페이스도 같다. 입력 검증을 너무 빡빡하게(예: 정규식 한 글자도 안 봐줌) 하면 정상 케이스도 거절된다. 너무 헐겁게(예: 검증 없음) 하면 잘못된 데이터가 시스템 깊숙이 들어와 폭발한다. 적절한 허용 오차가 핵심이다.
# 잘못된 예 — 너무 빡빡
def parse_phone(s: str) -> str:
if not re.fullmatch(r"010-\d{4}-\d{4}", s): # 하이픈 없는 입력 거절
raise ValueError("Invalid")
return s
# 잘못된 예 — 너무 헐거움
def parse_phone(s: str) -> str:
return s # 검증 없이 그대로 받음 → "abc123" 같은 게 DB까지 도달
# 올바른 예 — 적절한 허용 오차
def parse_phone(s: str) -> str:
digits = re.sub(r"\D", "", s) # 숫자만 추출 (하이픈 공백 괄호 허용)
if not re.fullmatch(r"010\d{8}", digits):
raise ValueError("Korean mobile required")
return f"{digits[:3]}-{digits[3:7]}-{digits[7:]}" # 정규형으로
핵심 개념 정리
| 개념 | 한 줄 설명 |
|---|---|
| 결합(coupling) | "함께 고정" = 두 구성요소의 연결 관계. 제거가 아니라 관리 대상 |
| 공유 수명주기 | 같이 빌드·테스트·배포되는 정도. 모놀리스↑ / 별 서비스↓ |
| 공유 지식 | 한쪽이 다른 쪽의 무엇을 알아야 하는가. 적을수록 변경 전파↓ |
| 암묵적 결합 | 인터페이스에 명시되지 않은 가정. 가장 위험 |
| 상위/하위 | 상위=기능 제공, 하위=소비. 지식 흐름은 상위→하위 |
| 시스템 3요소 | 구성요소 + 상호작용(=결합) + 목적. 셋이 서로 의존 |
| 필수/우연 결합 | 목적상 필요한가, 설계 부주의인가. 후자만 제거 |
| 허용 오차 | 인터페이스가 받아들이는 입력의 폭. 너무 빡빡/헐거우면 둘 다 문제 |
| 캡슐화 경계 | 구성요소가 외부에 드러내는 면과 숨기는 면의 구분선 |
| 프랙털 본질 | 같은 결합 원리가 시스템 모든 계층(회사→서비스→클래스→메서드)에 적용됨 |
실무 체크리스트
- [ ] 새 의존성을 추가하기 전에 "이건 필수 결합인가, 우연 결합인가" 자문했나?
- [ ] 두 모듈을 같은 배포 단위에 둘지 결정할 때 수명주기 결합의 비용을 따져 봤나?
- [ ] 인터페이스가 구체 제품(MySQL, Kafka)을 노출하는가, 의도(save/query, publish/subscribe)를 노출하는가?
- [ ] 코드에 "이건 항상 X일 거야" 같은 암묵 가정이 인터페이스로 끌어올려져 있나?
- [ ] 상위 인터페이스를 바꿀 때, 깨질 모든 하위를 식별했나?
- [ ] 입력 검증의 허용 오차가 정상 케이스를 막거나 잘못된 케이스를 통과시키지 않는지 한계 케이스를 적어 봤나?
- [ ] 시스템 설계 시 "상자(구성요소)"만큼이나 "상자 사이의 선(상호작용=결합)"에도 충분히 주의를 기울였나?
연습문제 (문제만 — 정답은 부록 D)
- 개념. 본문에 따르면 결합이 0인 시스템이 왜 불가능하고 왜 무용한가? 두 이유를 각각 한 문장으로 답하라.
- 분석. 같은 회사가 사내 결제 모듈을 (a) 모놀리스에 내장, (b) 별 마이크로서비스로 분리하는 두 방안을 두고 고민 중이다. 수명주기 결합 관점에서 둘의 차이를 설명하고, 어느 쪽이 "결합 강도가 높다"고 말할 수 있는지 답하라.
- 재설계. 다음 코드는
OrderService가 PostgreSQL이라는 구체 제품을 안다. 공유 지식을 줄이는 두 단계 리팩토링(B 단계·C 단계)을 코드로 보여라.python class OrderService: def __init__(self, db: PostgreSQLConnection): self.db = db def place(self, order): self.db.execute("INSERT INTO orders ...") - 판단. 한 팀이 "마이크로서비스 간 직접 HTTP 호출 대신 카프카 메시지 큐로 다 바꾸자"고 한다. 이게 항상 결합을 줄이는 행동인가? 카프카 도입이 새로 만드는 암묵적 결합의 예를 두 가지 들어라.
- 적용. 일상 시스템 하나(예: 학교의 식권 발급 절차)에서 상위/하위 관계와 지식 흐름을 식별하라. 누가 어떤 인터페이스를 통해 무엇을 알고 있어야 하는가?
최신 동향 (2026-05 기준)
최신 동향 (검증 2026-05-26) — 결합·시스템 설계의 원리는 이 책 내용이 그대로 유효하다. 다만 결합을 측정·시각화하는 도구가 빠르게 성숙 중이다.
- 모듈 간 결합 자동 시각화 도구 보편화. 정적 분석으로 import/호출 그래프를 그려 우연 결합 핫스폿을 찾는다. Sourcegraph · Sourcetrail · CodeScene 같은 도구가 자주 쓰인다.
- 모놀리스 → 마이크로서비스 분리 가이드는 더 신중해졌다. 분리 후 수명주기 결합은 줄지만 지식 결합(스키마·계약·관측)이 오히려 복잡해질 수 있다는 사례가 누적돼, Microservices.io 같은 카탈로그가 "Strangler Fig·Service Decomposition" 패턴을 기준으로 자리잡았다.
부록 A. 용어 사전
| 한글 용어 | 원문 영문명 | 의미 |
|---|---|---|
| 결합 | Coupling | 두 구성요소가 연결돼 서로 영향을 주는 관계 |
| 공유 수명주기 | Shared Lifecycle | 함께 빌드·테스트·배포되는 결합 형태 |
| 공유 지식 | Shared Knowledge | 한쪽이 다른 쪽 내부에 대해 알아야 하는 양 |
| 캡슐화 경계 | Encapsulation Boundary | 모듈이 외부에 드러내는 면과 숨기는 면의 구분선 |
| 상위 | Upstream | 기능을 제공하는 쪽 |
| 하위 | Downstream | 기능을 소비하는 쪽 |
| 지식 흐름 | Knowledge Flow | 인터페이스로 노출된 정보의 이동 방향(상위→하위) |
| 시스템 | System | "무언가를 달성하는 방식으로 구성된 상호 연결된 요소들의 집합" (메도즈) |
| 필수 결합 | Essential Coupling | 시스템 목적상 반드시 필요한 결합 |
| 우연 결합 | Accidental Coupling | 설계 부주의로 생긴 결합. 제거 대상 |
| 허용 오차 | Tolerance | 결합부가 받아들이는 변화의 폭 |
| 암묵적 지식 | Implicit Knowledge | 인터페이스에 명시되지 않은 채 공유되는 가정 |
| 프랙털 구조 | Fractal Structure | 같은 패턴이 다른 규모에서 반복되는 구조. 소프트웨어 계층 전체에 같은 결합 원리 적용 |
부록 B. 핵심 비교표
결합의 두 동인 비교
| 구분 | 공유 수명주기 | 공유 지식 |
|---|---|---|
| 측정 기준 | 같이 빌드·배포 빈도 | 인터페이스로 노출된 정보 양 |
| 결정 요소 | 배포 단위(모놀리스/서비스) | 추상화 수준(구체 제품/추상 의도) |
| 강함 사례 | 같은 모놀리스의 두 모듈 | MySQLRepository 직접 의존 |
| 약함 사례 | 별 마이크로서비스로 분리 | IRepository(save/query) 의존 |
같은 기능, 다른 공유 지식 — 3단계 비교
| 단계 | 인터페이스 | CustomersService가 아는 것 | 변경 영향 |
|---|---|---|---|
| A | MySQLRepository.execute_sql(...) |
DB 제품(MySQL) + SQL 문법 | DB 제품 바꾸면 깨짐 |
| B | IRepository.execute_sql(...) |
DB 집단(관계형) + SQL 문법 | NoSQL로 바꾸면 깨짐 |
| C | IRepository.save(e) / query(c) |
"저장이 필요하다"는 의도뿐 | 거의 안 깨짐 |
필수 결합 vs 우연 결합 비교
| 구분 | 필수 결합 | 우연 결합 |
|---|---|---|
| 발생 원인 | 시스템 목적에서 비롯 | 설계 부주의·기술 선택 실수 |
| 예시 | 주문 서비스 → 결제 서비스 호출 | HTTP 핸들러가 DB 컬럼명 직접 참조 |
| 처방 | 신중하게 관리 | 제거 |
부록 C. 추천 참고 자료 & 링크
Tier 1 공식·표준 (생존 확인 2026-05-26)
| 자료 | 링크 |
|---|---|
| 책 공식 (제이펍) | jpub.kr |
| 원서 출판사 — Manning | Balancing Coupling in Software Design |
| 마이크로서비스 패턴 카탈로그 (Chris Richardson) | microservices.io/patterns |
| 메도즈 — Thinking in Systems | chelseagreen.com |
| 사이드 자료 — Ruth Malan 시스템 설계 | ruthmalan.com |
책 다른 장 안내
| 자료 | 설명 |
|---|---|
| 2장 | 커네빈 프레임워크로 복잡성 정의 |
| 3장 | 자유도·제약으로 본 복잡성과 상호작용 |
| 4장 | 모듈성 — 결합의 반대편 |
| 8장 (별권) | "거리" — 수명주기 결합의 구조적 요인 |
| 12장 (별권) | 소프트웨어 설계의 프랙털 기하학 |
부록 D. 연습문제 풀이
-
(불가능: 시스템 자체가 성립 안 됨 / 무용: 부품 더미일 뿐 목적 달성 불가) 두 구성요소가 협력하려면 최소한의 지식 공유가 필요하다. 결합이 0이면 호출도 메시지도 못 보내 시스템이 동작하지 않는다. 또한 메도즈 정의상 시스템은 상호 연결된 요소들의 집합이므로, 연결이 0이면 정의상 시스템이 아닌 부품 더미다 — 목적을 달성할 수 없다.
-
(b 마이크로서비스 분리가 수명주기 결합이 약함, 즉 a 모놀리스가 결합 강도가 높다) 모놀리스 내장(a)은 결제 모듈을 전체 시스템과 함께 빌드·테스트·배포해야 하므로 수명주기 결합이 강하다. 마이크로서비스 분리(b)는 결제 서비스를 독립적으로 배포할 수 있어 수명주기 결합이 약하다. 따라서 "결합 강도가 높다"는 표현은 a에 해당한다. 단, 지식 결합은 별개 차원으로 같이 봐야 한다(분리해도 API 계약은 공유).
-
(B 단계 = 관계형 가정 유지 추상화, C 단계 = 의도만 노출)
```python # B 단계: 관계형 DB 추상화 — SQL은 여전히 노출 class IOrderDb: def execute(self, sql, params): ...
class OrderService: def init(self, db: IOrderDb): self.db = db def place(self, order): self.db.execute("INSERT INTO orders ...", (order.id, ...))
# C 단계: 의도만 — 저장 메커니즘 완전 캡슐화 class IOrderRepo: def save(self, order: Order): ...
class OrderService: def init(self, repo: IOrderRepo): self.repo = repo def place(self, order): self.repo.save(order) ```
-
(아니다 — 새로운 암묵적 결합이 생긴다) 카프카 도입은 동기 호출 결합은 줄이지만, ① 메시지 스키마 공유 — 발행자가 필드 이름이나 타입을 바꾸면 모든 구독자가 깨진다(인터페이스가 분산 시스템 전체에 퍼져 더 위험). ② 토픽 의미 가정 — 구독자는 "이 토픽은 항상 주문 생성 이벤트"라고 암묵 가정한다. 발행자가 의미를 슬쩍 바꾸면(예: 결제 완료도 같은 토픽으로) 조용히 잘못 동작한다. 즉 동기 결합 → 비동기 + 스키마 + 의미 결합으로 분산됐을 뿐 양적 결합은 늘 수 있다.
-
(예시 — 식권 발급) 학생(하위)은 식당(상위)이 정한 식권 발급 인터페이스(가격·결제 수단·발급 위치·운영 시간)를 알아야 식권을 받을 수 있다. 식당은 학생 개개인을 모르지만(역방향 흐름 없음), 가격·메뉴 같은 공개 정보는 학생에게 흘러간다(지식 흐름: 식당 → 학생). 학생이 모르는 내부 지식(원가 구조·재고 관리)은 캡슐화돼 있다. 식당이 가격을 바꾸면 학생은 영향받지만, 학생이 동아리에 가입해도 식당은 영향받지 않는다. 이게 상위·하위·지식 흐름의 비대칭이다.
클릭하거나 Space를 눌러 뒤집기